<?php
/** 
 * EGP Framework
 *
 * LICENSE
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * @author     Akon(番茄红了) <aultoale@gmail.com>
 * @copyright  Copyright (c) 2008 (http://www.tblog.com.cn)
 * @license    http://www.gnu.org/licenses/gpl.html     GPL 3
 */

!defined('EGP_LIB_DIR') && die('Access Deny!');

/**
 * Sql 查询语句生成
 *
 * @package    classes
 * @author     Akon(番茄红了) <aultoale@gmail.com>
 * @copyright  Copyright (c) 2008 (http://www.tblog.com.cn)
 * @license    http://www.gnu.org/licenses/gpl.html     GPL 3
 */
class EGP_Db_Select
{

    const DISTINCT       = 'distinct';
    const FOR_UPDATE     = 'forupdate';
    const COLUMNS        = 'columns';
    const FROM           = 'from';
    const WHERE          = 'where';
    const GROUP          = 'group';
    const HAVING         = 'having';
    const ORDER          = 'order';
    const LIMIT_COUNT    = 'limitcount';
    const LIMIT_OFFSET   = 'limitoffset';
    const INNER_JOIN     = 'inner join';
    const LEFT_JOIN      = 'left join';
    const RIGHT_JOIN     = 'right join';

    /**
     * 数据库连接驱动实例
     *
     * @var EGP_Db_Adapter_Abstract
     */
    private $_adapter = null;

    /**
     * 查询条件存储信息
     *
     * @var array
     */
    private $_parts = array();

    /**
     * 查询条件初始化信息
     *
     * @var    array
     */
    protected static $_partsInit = array(
        self::DISTINCT       => false,
        self::FOR_UPDATE     => false,
        self::COLUMNS        => array(),
        self::FROM           => array(),
        self::WHERE          => array(),
        self::GROUP          => array(),
        self::HAVING         => array(),
        self::ORDER          => array(),
        self::LIMIT_COUNT    => null,
        self::LIMIT_OFFSET   => null,
    );

    /**
     * 构造方法
     *
     * @param  EGP_Db_Adapter_Abstract  $adapter  数据库连接驱动实例
     */
    public function __construct(EGP_Db_Adapter_Abstract $adapter)
    {
        $this->_adapter = $adapter;
        $this->_parts = self::$_partsInit;
    }

    /**
     * 输出 SQL 语句
     *
     * @return string
     */
    public function __toString()
    {
        $sql = 'SELECT';
        $this->_parts[self::DISTINCT] && $sql .= ' DISTINCT';
        $sql .= "\n\t";

        if ($this->_parts[self::COLUMNS] instanceof EGP_Db_Expr) {
            $sql .= $this->_parts[self::COLUMNS]->__toString();
        } else {
            $columns = array();
            foreach ($this->_parts[self::COLUMNS] as $columnEntry) {
                list($correlationName, $column, $alias) = $columnEntry;
                if ($column instanceof EGP_Db_Expr) {
                    $columns[] = $this->_adapter->quoteAs($column, $alias);
                } else {
                    if ($column == '' || $column == '*') {
                        $column = new EGP_Db_Expr('*');
                        $alias = null;
                    }
                    if (empty($correlationName)) {
                        $columns[] = $this->_adapter->quoteAs($column, $alias);
                    } else {
                        $columns[] = $this->_adapter->quoteAs(array($correlationName, $column), $alias);
                    }
                }
            }
            $sql .= implode(",\n\t", $columns);
        }

        if ($this->_parts[self::FROM]) {
            $from = array();
            foreach ($this->_parts[self::FROM] as $correlationName => $table) {
                $tmp = '';
                if (empty($from)) {
                    !empty($table['schema'])
                        && $tmp .= $this->_adapter->quoteIdent($table['schema']) . '.';
                    $tmp .= $this->_adapter->quoteAs($table['tableName'], $correlationName);
                } else {
                    !empty($table['joinType'])
                        && $tmp .= strtoupper($table['joinType']) . ' ';
                    !empty($table['schema'])
                        && $tmp .= $this->_adapter->quoteIdent($table['schema']) . '.';
                    $tmp .= $this->_adapter->quoteAs($table['tableName'], $correlationName);
                    !empty($table['joinCondition']) && $tmp .= ' ON ' . $table['joinCondition'];
                }
                $from[] = $tmp;
            }
            !empty($from) && $sql .= "\nFROM " . implode("\n", $from);

            if ($this->_parts[self::WHERE]) {
                $sql .= "\nWHERE\n\t";
                $sql .= implode("\n\t", $this->_parts[self::WHERE]);
            }

            if ($this->_parts[self::GROUP]) {
                $sql .= "\nGROUP BY\n\t";
                $l = array();
                foreach ($this->_parts[self::GROUP] as $term)
                    $l[] = $this->_adapter->quoteIdent($term);
                $sql .= implode(",\n\t", $l);
            }

            if ($this->_parts[self::HAVING]) {
                $sql .= "\nHAVING\n\t";
                $sql .= implode("\n\t", $this->_parts[self::HAVING]);
            }
        }

        if ($this->_parts[self::ORDER]) {
            $sql .= "\nORDER BY\n\t";
            $l = array();
            foreach ($this->_parts[self::ORDER] as $term) {
                $l[] = is_array($term)
                     ? $this->_adapter->quoteIdent($term[0]) . ' ' . $term[1]
                     : $this->_adapter->quoteIdent($term);
            }
            $sql .= implode(",\n\t", $l);
        }

        $count = 0;
        $offset = 0;
        if (!empty($this->_parts[self::LIMIT_OFFSET])) {
            $offset = (integer) $this->_parts[self::LIMIT_OFFSET];
            $count = intval(9223372036854775807);
        }

        if (!empty($this->_parts[self::LIMIT_COUNT]))
            $count = (integer) $this->_parts[self::LIMIT_COUNT];

        if ($count > 0)
            $sql = trim($this->_adapter->limit($sql, $count, $offset));

        $this->_parts[self::FOR_UPDATE] && $sql .= ' FOR UPDATE';

        return $sql;
    }

    /**
     * 是否开启 DISTINCT
     *
     * @param  boolean  $flag
     * @return EGP_Db_Select
     */
    public function distinct($flag = true)
    {
        $this->_parts[self::DISTINCT] = (boolean) $flag;
        return $this;
    }

    /**
     * 是否开启 FOR UPDATE
     *
     * @param  boolean  $flag
     * @return EGP_Db_Select
     */
    public function forUpdate($flag = true)
    {
        $this->_parts[self::FOR_UPDATE] = (boolean) $flag;
        return $this;
    }

    /**
     * SQL 语句 FROM
     *
     * @param  mixed   $name    表名
     * @param  mixed   $cols    字段名(默认为 '*')
     * @param  string  $schema  数据库名
     * @return EGP_Db_Select
     */
    public function from($name, $cols = '*', $schema = null)
    {
        return $this->joinInner($name, null, $cols, $schema);
    }

    /**
     * 设定SQL语句查询列
     *
     * @param  string   $cols  表名
     * @return EGP_Db_Select
     */
    public function columns($cols = '*')
    {
        $this->_parts[self::COLUMNS] = new EGP_Db_Expr((string) $cols);
        return $this;
    }

    /**
     * SQL 语句 JOIN
     *
     * @param  mixed   $name    表名
     * @param  string  $cond    JOIN 条件
     * @param  mixed   $cols    字段名(默认为 '*')
     * @param  string  $schema  数据库名
     * @return EGP_Db_Select
     */
    public function join($name, $cond, $cols = '*', $schema = null)
    {
        return $this->joinInner($name, $cond, $cols, $schema);
    }

    /**
     * SQL 语句 INNER JOIN
     *
     * @param  mixed   $name    表名
     * @param  string  $cond    JOIN 条件
     * @param  mixed   $cols    字段名(默认为 '*')
     * @param  string  $schema  数据库名
     * @return EGP_Db_Select
     */
    public function joinInner($name, $cond, $cols = '*', $schema = null)
    {
        return $this->_join(self::INNER_JOIN, $name, $cond, $cols, $schema);
    }

    /**
     * SQL 语句 LEFT JOIN
     *
     * @param  mixed   $name    表名
     * @param  string  $cond    JOIN 条件
     * @param  mixed   $cols    字段名(默认为 '*')
     * @param  string  $schema  数据库名
     * @return EGP_Db_Select
     */
    public function joinLeft($name, $cond, $cols = '*', $schema = null)
    {
        return $this->_join(self::LEFT_JOIN, $name, $cond, $cols, $schema);
    }

    /**
     * SQL 语句 RIGHT JOIN
     *
     * @param  mixed   $name    表名
     * @param  string  $cond    JOIN 条件
     * @param  mixed   $cols    字段名(默认为 '*')
     * @param  string  $schema  数据库名
     * @return EGP_Db_Select
     */
    public function joinRight($name, $cond, $cols = '*', $schema = null)
    {
        return $this->_join(self::RIGHT_JOIN, $name, $cond, $cols, $schema);
    }

    /**
     * 设定 JOIN 信息
     *
     * @param  string  $type    JOIN 类型
     * @param  mixed   $name    表名
     * @param  string  $cond    JOIN 条件
     * @param  mixed   $cols    字段名(默认为 '*')
     * @param  string  $schema  数据库名
     * @return EGP_Db_Select
     * @throws EGP_Exception
     */
    protected function _join($type, $name, $cond, $cols, $schema = null)
    {
        $joinTypes = array(self::INNER_JOIN, self::LEFT_JOIN, self::RIGHT_JOIN);
        if (!in_array($type, $joinTypes)) {
            throw new EGP_Exception("Invalid join type '$type'");
        }
        if (empty($name)) {
            $correlationName = $tableName = '';
        } elseif (is_array($name)) {
            foreach ($name as $correlationName => $tableName)
                break;
        } elseif ($name instanceof EGP_Db_Expr) {
            $tableName = $name;
            $correlationName = $name;
        } else if (preg_match('/^(.+)\s+AS\s+(.+)$/i', $name, $m)) {
            $tableName = $m[1];
            $correlationName = $m[2];
        } else {
            $tableName = $name;
            $correlationName = $this->_adapter->quotePrefix($name);
        }
        $this->_parts[self::FROM][$correlationName] = array(
            'schema'        => $schema,
            'joinType'      => $type,
            'tableName'     => $this->_adapter->quotePrefix($tableName),
            'joinCondition' => $cond
        );
        !is_array($cols) && $cols = array($cols);
        $correlationName == null && $correlationName = '';
        foreach ($cols as $alias => $col) {
            $currentCorrelationName = $correlationName;
            if (is_string($col)) {
                if (preg_match('/^(.+)\s+AS\s+(.+)$/i', $col, $m)) {
                    $col = $m[1];
                    $alias = $m[2];
                }
                if (preg_match('/\(.*\)/', $col)) {
                    $col = new EGP_Db_Expr($col);
                } elseif (preg_match('/(.+)\.(.+)/', $col, $m)) {
                    $currentCorrelationName = $m[1];
                    $col = $m[2];
                }
            }
            $this->_parts[self::COLUMNS][] = array(
                $currentCorrelationName,
                $col,
                is_string($alias) ? $alias : null
            );
        }
        return $this;
    }

    /**
     * SQL 语句 WHERE (AND)
     *
     * @param  string  $cond  WHERE 条件
     * @return EGP_Db_Select
     */
    public function where($cond)
    {
        $num = func_num_args();
        if ($num == 2) {
            $val = func_get_arg(1);
            $cond = $this->_adapter->quoteInto($cond, $val);
        } elseif ($num > 2) {
            $args = func_get_args();
            $cond = call_user_func_array(array($this->_adapter, 'quoteInto'), $args);
        }
        $this->_parts[self::WHERE][] = $this->_parts[self::WHERE] ? "AND ($cond)" : "($cond)";
        return $this;
    }

    /**
     * SQL 语句 WHERE (OR)
     *
     * @param  string  $cond  WHERE 条件
     * @return EGP_Db_Select
     */
    public function orWhere($cond)
    {
        $num = func_num_args();
        if ($num == 2) {
            $val = func_get_arg(1);
            $cond = $this->_adapter->quoteInto($cond, $val);
        } elseif ($num > 2) {
            $args = func_get_args();
            $cond = call_user_func_array(array($this->_adapter, 'quoteInto'), $args);
        }
        $this->_parts[self::WHERE][] = $this->_parts[self::WHERE] ? "OR ($cond)" : "($cond)";
        return $this;
    }

    /**
     * SQL 语句 GROUP
     *
     * @param  mixed  $spec  GROUP 条件
     * @return EGP_Db_Select
     */
    public function group($spec)
    {
        !is_array($spec) && $spec = array($spec);
        foreach ($spec as $val)
            $this->_parts[self::GROUP][] = $val;
        return $this;
    }

    /**
     * SQL 语句 HAVING (AND)
     *
     * @param  mixed  $cond  HAVING 条件
     * @return EGP_Db_Select
     */
    public function having($cond)
    {
        if (func_num_args() > 1) {
            $val = func_get_arg(1);
            $cond = $this->_adapter->quoteInto($cond, $val);
        }
        $this->_parts[self::HAVING][] = $this->_parts[self::HAVING] ? "AND ($cond)" : "($cond)";
        return $this;
    }

    /**
     * SQL 语句 HAVING (OR)
     *
     * @param  mixed  $cond  HAVING 条件
     * @return EGP_Db_Select
     */
    public function orHaving($cond)
    {
        if (func_num_args() > 1) {
            $val = func_get_arg(1);
            $cond = $this->_adapter->quoteInto($cond, $val);
        }
        $this->_parts[self::HAVING][] = $this->_parts[self::HAVING] ? "OR ($cond)" : "($cond)";
        return $this;
    }

    /**
     * SQL 语句 ORDER
     *
     * @param  mixed  $spec  ORDER 条件
     * @return EGP_Db_Select
     */
    public function order($spec)
    {
        !is_array($spec) && $spec = array($spec);
        foreach ($spec as $val) {
            if (empty($val))
                continue;
            $direction = 'ASC';
            if (preg_match('/(.*\W)(ASC|DESC)\b/is', $val, $matches)) {
                $val = trim($matches[1]);
                $direction = $matches[2];
            }
            $this->_parts[self::ORDER][] = array($val, $direction);
        }
        return $this;
    }

    /**
     * SQL 语句 LIMIT
     *
     * @param  integer  $count   返回的记录条数
     * @param  integer  $offset  起始偏移记录数
     * @return EGP_Db_Select
     */
    public function limit($count = null, $offset = null)
    {
        $this->_parts[self::LIMIT_COUNT]  = (integer) $count;
        $this->_parts[self::LIMIT_OFFSET] = (integer) $offset;
        return $this;
    }

    /**
     * SQL 语句 LIMIT 分页
     *
     * @param  integer  $currentPage  当前页数
     * @param  integer  $pagesize     每页显示记录数
     * @return EGP_Db_Select
     */
    public function limitPage($currentPage, $pageSize)
    {
        $currentPage = ($currentPage > 0) ? $currentPage : 1;
        $pageSize    = ($pageSize > 0)    ? $pageSize    : 1;
        $this->_parts[self::LIMIT_COUNT]  = (integer) $pageSize;
        $this->_parts[self::LIMIT_OFFSET] = (integer) $pageSize * ($currentPage - 1);
        return $this;
    }

    /**
     * 获取指定的 SQL 子句
     *
     * @param  string  $part  SQL 子句名称
     * @return array
     * @throws EGP_Exception
     */
    public function getPart($part = null)
    {
        $part = strtolower($part);
        if (!array_key_exists($part, $this->_parts))
            throw new EGP_Exception("无效的 SQL 子句： '$part'");
        return $this->_parts[$part];
    }

    /**
     * 重置指定的 SQL 子句
     *
     * @param  string  $part  SQL 子句名称(默认为所有)
     * @return EGP_Db_Select
     */
    public function reset($part = null)
    {
        if ($part == null)
            $this->_parts = self::$_partsInit;
        elseif (array_key_exists($part, self::$_partsInit))
            $this->_parts[$part] = self::$_partsInit[$part];
        return $this;
    }

    /**
     * 执行一条 SQL 查询语句
     *
     * @return resource
     */
    public function query()
    {
        return $this->_adapter->query($this->__toString());
    }

    /**
     * 取出所有资料
     *
     * @return resource
     */
    public function fetchAll()
    {
        return $this->_adapter->fetchAll($this->__toString());
    }

    /**
     * 取出所有数据，并作为关联数组及数字数组返回
     *
     * @return resource
     */
    public function fetchArray()
    {
        return $this->_adapter->fetchArray($this->__toString());
    }

    /**
     * 取出所有数据，并作为数组对象返回
     *
     * @return resource
     */
    public function fetchObj()
    {
        return $this->_adapter->fetchObj($this->__toString());
    }

    /**
     * 取出一行数据，并以为数组返回
     *
     * @return array
     */
    public function fetchRow()
    {
        return $this->_adapter->fetchRow($this->__toString());
    }

    /**
     * 取出第一行数据，并返回第一个字段的值
     *
     * @return mixed
     */
    public function fetchOne()
    {
        return $this->_adapter->fetchOne($this->__toString());
    }

    /**
     * 取得结果集中指定列的内容
     *
     * @return integer
     */
    public function result($row)
    {
        return $this->_adapter->numRows($this->__toString(), $row);
    }

    /**
     * 取得结果集中行的数目
     *
     * @return integer
     */
    public function numRows()
    {
        return $this->_adapter->numRows($this->__toString());
    }

}